package cn.gx.learn.security_boot_jwt_websocket.appiontment.service;

import cn.gx.learn.security_boot_jwt_websocket.appiontment.controller.vo.AppiontmentReqVO;
import cn.gx.learn.security_boot_jwt_websocket.appiontment.dal.convert.AppiontmentConvert;
import cn.gx.learn.security_boot_jwt_websocket.appiontment.dal.data.AppiontmentDO;
import cn.gx.learn.security_boot_jwt_websocket.appiontment.dal.data.wrapper.AppiontmentRDO;
import cn.gx.learn.security_boot_jwt_websocket.appiontment.dal.data.wrapper.AppiontmentWrapper;
import cn.gx.learn.security_boot_jwt_websocket.appiontment.dal.mysql.AppiontMapper;
import cn.gx.learn.security_boot_jwt_websocket.springsecurity.config.SpringSecurityConfig;
import cn.gx.learn.security_boot_jwt_websocket.springsecurity.dal.UserDetail;
import cn.gx.learn.security_boot_jwt_websocket.util.RedissonLockUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class AppiontmentServiceImpl extends ServiceImpl<AppiontMapper,AppiontmentDO> implements AppiontmentService{

    @Autowired
    private AppiontMapper appiontMapper;

    @Autowired
    private RedissonLockUtil redissonLockUtil;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public String ceateAppiontment(AppiontmentReqVO reqVO) {

        String lock="lock"+reqVO.getCreateTime().toLocalDate().toString();
        boolean lockquired=false;
        try{
            lockquired=redissonLockUtil.trylock(lock,5,-1,TimeUnit.SECONDS);
            if (!lockquired){
                return "锁异常，稍后重试";
            }
            if(!noConflict(reqVO))return "预约时间冲突";
            makeAppionment(reqVO);
            return "success";
        }finally {
            if (lockquired) {
                redissonLockUtil.unlock(lock);
            }
        }

    }


    private Long makeAppionment(AppiontmentReqVO reqVO){

        AppiontmentDO appiontmentDO= AppiontmentConvert.INSTANCE.convert(reqVO);
        appiontmentDO.setStatus(0);
        appiontmentDO.setUserId(((UserDetail)SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUser().getId().longValue());
        appiontmentDO.setDay(reqVO.getCreateTime().toLocalDate());
        appiontMapper.insert(appiontmentDO);
        updateRedis(appiontmentDO);
        return appiontmentDO.getId();
    }

    private void updateRedis(AppiontmentDO appiontmentDO) {
        AppiontmentRDO appiontmentRDO=AppiontmentConvert.INSTANCE.convertToRDO(appiontmentDO);
        String lock=appiontmentDO.getCreateTime().toLocalDate().toString();
        String temp=redisTemplate.opsForValue().get(lock);
        if (temp==null){
            AppiontmentWrapper appiontmentWrapper=new AppiontmentWrapper();
            appiontmentWrapper.add(appiontmentRDO);
            redisTemplate.opsForValue().set(lock,JSON.toJSONString(appiontmentWrapper));
        }else {
            AppiontmentWrapper appiontmentWrapper = JSON.parseObject(temp, AppiontmentWrapper.class);
            appiontmentWrapper.add(appiontmentRDO);
            redisTemplate.opsForValue().set(lock, JSON.toJSONString(appiontmentWrapper));
        }
    }
    private void updateRedis(List<AppiontmentDO> appiontmentDOs) {
        List<AppiontmentRDO> appiontmentRDOs=AppiontmentConvert.INSTANCE.convertToRDO(appiontmentDOs);
        String lock=appiontmentDOs.getFirst().getCreateTime().toLocalDate().toString();
        AppiontmentWrapper appiontmentWrapper=new AppiontmentWrapper(appiontmentRDOs);
        redisTemplate.opsForValue().set(lock,JSON.toJSONString(appiontmentWrapper));
    }

    private boolean noConflict(AppiontmentReqVO reqVO){
        boolean flag=true;
        String lock=reqVO.getCreateTime().toLocalDate().toString();
        String temp=redisTemplate.opsForValue().get(lock);
        if(temp==null||temp.isEmpty()){
            System.out.println("------缓存没有数据的分支-----");
            List<AppiontmentDO> appiontmentDOList=appiontMapper.selectList(new LambdaQueryWrapper<AppiontmentDO>().eq(AppiontmentDO::getDay,lock));
            if(appiontmentDOList!=null&& !appiontmentDOList.isEmpty()) {
                //这里更新缓存需要同步执行，单个线程获取到锁进入判断之后，查库没有，更新缓存，异步的话放开锁之后新的线程进来存在再次查库执行更新的问题
                //存在线程1执行了插库，异步更新缓存落后，新的线程进来，再查库（有前一个线程的插入）更新缓存，然后前一个异步才执行，覆盖了缓存（没有线程当前查库记录的），丢了新的预约信息
                updateRedis(appiontmentDOList);
                for (AppiontmentDO appiontmentDO : appiontmentDOList) {
                    if (!isCorrectTime(appiontmentDO.getCreateTime(), appiontmentDO.getEndTime(), reqVO.getCreateTime(),reqVO.getEndTime())) {
                        flag = false;
                        break;
                    }
                }
            }
        }else {
            System.out.println("------缓存有数据的分支-----");
            AppiontmentWrapper appiontmentWrapper = JSON.parseObject(temp, AppiontmentWrapper.class);
            for (int i = 0; i < appiontmentWrapper.getAppiontmentRDOList().size(); i++) {
                if (!isCorrectTime(appiontmentWrapper.getAppiontmentRDOList().get(i).getCreateTime(), appiontmentWrapper.getAppiontmentRDOList().get(i).getEndTime(),reqVO.getCreateTime(),reqVO.getEndTime())) {
                    flag = false;
                    break;
                }
            }
        }
        return flag;
    }

    private boolean isCorrectTime(LocalDateTime s1, LocalDateTime e1, LocalDateTime s2, LocalDateTime e2) {

        if(s1==null||e1==null||s2==null||e2==null){
            throw new RuntimeException("比较时间存在空值！");
        }
        if(s1.isAfter(e1)||s2.isAfter(e2))return false;
        else if ( (s1.isBefore(e2)&& e1.isAfter(e2))  ||  (s1.isBefore(s2)&&e1.isAfter(s2))  || (e2.isEqual(e1)) || (s1.isEqual(s2)) ) {

            return false;
        }

        return true;

    }



}
